package queue

import (
	"sync"
)

// 要做到两点：1、能判断任务是否已在执行中，2、能阻塞其他 goroutine，并在阻塞结束后拿到执行结果。
// 是否已在执行可以用一个状态变量，同时管理多组状态变量可以用 map，
// 阻塞 goroutine 可以用 WaitGroup。
// 每一组状态内包括：WaitGroup、返回值、返回错误信息，可以组成一个 struct，作为 map 中 key 对应的值。

// var errGoexit = errors.New("runtime.Goexit was called")

// type panicError struct {
// 	value interface{}
// 	stack []byte
// }

// func (p *panicError) Error() string {
// 	return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
// }

// func (p *panicError) Unwrap() error {
// 	err, ok := p.value.(error)
// 	if !ok {
// 		return nil
// 	}

// 	return err
// }

type Group struct {
	mu sync.Mutex
	// 保存 key 对应的函数执行过程和结果
	m map[string]*call
}

type call struct {
	// 控制只有一个 goroutine 在执行
	wg sync.WaitGroup
	// 执行结果
	val interface{}
	err error
	// forgotten bool
	// 同时执行同个 key 的 goroutine 数量
	dups  int
	chans []chan<- Result
}

type Result struct {
	Val interface{}
	Err error
	// Shared bool
}

func (g *Group) DoChan(key string, fn func() (any, error)) <-chan Result {
	ch := make(chan Result)
	g.mu.Lock()
	if g.m == nil {
		g.m = make(map[string]*call)
	}
	c, ok := g.m[key]
	if ok {
		c.chans = append(c.chans, ch)
		g.mu.Unlock()
		return ch
	}
	c = &call{}
	g.m[key] = c
	c.chans = append(c.chans, ch)
	c.wg.Add(1)
	g.mu.Unlock()

	go g.doCall(c, key, fn)
	return ch
}

func (g *Group) Do(key string, fn func() (any, error)) (v any, e error, shared bool) {
	g.mu.Lock()
	if g.m == nil {
		g.m = make(map[string]*call)
	}
	if c, ok := g.m[key]; ok {
		// key 存在说明已有 goroutine 在执行
		c.dups++
		g.mu.Unlock()
		c.wg.Wait()
		// if e, ok := c.err.(*panicError); ok {
		// 	panic(e)
		// } else if c.err == errGoexit {
		// 	runtime.Goexit()
		// }
		return c.val, c.err, true
	}
	c := new(call)
	// 告诉其他 goroutine 不许要执行
	g.m[key] = c
	// 让其他 goroutine 阻塞等结果
	c.wg.Add(1)
	g.mu.Unlock()
	// 自己执行
	g.doCall(c, key, fn)
	return c.val, c.err, c.dups > 0
}

func (g *Group) doCall(c *call, key string, fn func() (interface{}, error)) {
	c.val, c.err = fn()

	g.mu.Lock()
	// 告诉其他 goroutine 拿结果
	c.wg.Done()
	// 删除 key 进入下一轮
	delete(g.m, key)
	// 必须删除 key 之后才能通知 channel
	for _, ch := range c.chans {
		ch <- Result{c.val, c.err}
	}
	g.mu.Unlock()
}
